#include <stdio.h>
#include <stdint.h>

#include "cpu.h"
//#include "translate.h"
#include "access.h"

void raise_exception_err(int exception_index, int error_code)
{
	printf("raise exception %d, %08x\n", exception_index, error_code);
}

/* return non zero if error */
static inline int load_segment(CPUX86State *env, uint32_t *e1_ptr, uint32_t *e2_ptr,
	uint32_t selector)
{
	SegmentCache *dt;
	int index;
	uint32_t ptr;
	
	if (selector & 0x4)
		dt = &env->ldt;
	else
		dt = &env->gdt;
	index = selector & ~7;
	if ((index + 7) > dt->limit)
		return -1;
	ptr = dt->base + index;
	*e1_ptr = ldl_kernel(ptr);
	*e2_ptr = ldl_kernel(ptr + 4);
	return 0;
}

static inline uint32_t get_seg_limit(uint32_t e1, uint32_t e2)
{
	uint32_t limit;
	limit = (e1 & 0xffff) | (e2 & 0x000f0000);
	if (e2 & DESC_G_MASK)
		limit = (limit << 12) | 0xfff;
	return limit;
}

static inline uint32_t get_seg_base(uint32_t e1, uint32_t e2)
{
	return ((e1 >> 16) | ((e2 & 0xff) << 16) | (e2 & 0xff000000));
}

/* only works if protected mode and not VM86. seg_reg must be != R_CS */
void load_seg_without_cs(CPUX86State *env, uint32_t seg_reg, uint32_t selector)
{
	uint32_t e1, e2;
	int cpl, dpl, rpl;
	SegmentCache *dt;
	int index;
	uint32_t ptr;

	selector &= 0xffff;
	cpl = env->hflags & HF_CPL_MASK;
	if ((selector & 0xfffc) == 0) {
		/* null selector case */
		if (seg_reg == R_SS)
			raise_exception_err(EXCP0D_GPF, 0);
		cpu_load_seg_cache(env, seg_reg, selector, 0, 0, 0);
	} else {
		if (selector & 0x4)
			dt = &env->ldt;
		else
			dt = &env->gdt;
		index = selector & ~7;
		if ((index + 7) > dt->limit)
			raise_exception_err(EXCP0D_GPF, selector & 0xfffc);
		ptr = dt->base + index;
		e1 = ldl_kernel(ptr);
		e2 = ldl_kernel(ptr + 4);

		if (!(e2 & DESC_S_MASK))
			raise_exception_err(EXCP0D_GPF, selector & 0xfffc);
		rpl = selector & 3;
		dpl = (e2 >> DESC_DPL_SHIFT) & 3;
		if (seg_reg == R_SS) {
			/* must be writable segment */
			if ((e2 & DESC_CS_MASK) || !(e2 & DESC_W_MASK))
				raise_exception_err(EXCP0D_GPF, selector & 0xfffc);
			if (rpl != cpl || dpl != cpl)
				raise_exception_err(EXCP0D_GPF, selector & 0xfffc);
		} else {
			/* must be readable segment */
			if ((e2 & (DESC_CS_MASK | DESC_R_MASK)) == DESC_CS_MASK)
				raise_exception_err(EXCP0D_GPF, selector & 0xfffc);

			if (!(e2 & DESC_CS_MASK) || !(e2 & DESC_C_MASK)) {
				/* if not conforming code, test rights */
				if (dpl < cpl || dpl < rpl)
					raise_exception_err(EXCP0D_GPF, selector & 0xfffc);
			}
		}

		if (!(e2 & DESC_P_MASK)) {
			if (seg_reg == R_SS)
				raise_exception_err(EXCP0C_STACK, selector & 0xfffc);
			else
				raise_exception_err(EXCP0B_NOSEG, selector & 0xfffc);
		}

		/* set the access bit if not already set */
		if (!(e2 & DESC_A_MASK)) {
			e2 |= DESC_A_MASK;
			stl_kernel(ptr + 4, e2);
		}

		cpu_load_seg_cache(env, seg_reg, selector,
			get_seg_base(e1, e2),
			get_seg_limit(e1, e2),
			e2);
	}
}

/* protected mode jump */
void ljmp_protected(CPUX86State *env, uint32_t new_cs, uint32_t new_eip)
{
	uint32_t e1, e2, cpl, dpl, rpl, limit;

	e1 = e2 = 0;
	if ((new_cs & 0xfffc) == 0)
		raise_exception_err(EXCP0D_GPF, 0);
	if (load_segment(env, &e1, &e2, new_cs) != 0)
		raise_exception_err(EXCP0D_GPF, new_cs & 0xfffc);
	cpl = env->hflags & HF_CPL_MASK;
	if (e2 & DESC_S_MASK) {
		if (!(e2 & DESC_CS_MASK))
			raise_exception_err(EXCP0D_GPF, new_cs & 0xfffc);
		dpl = (e2 >> DESC_DPL_SHIFT) & 3;
		if (e2 & DESC_C_MASK) {
			/* conforming code segment */
			if (dpl > cpl)
				raise_exception_err(EXCP0D_GPF, new_cs & 0xfffc);
		} else {
			/* non conforming code segment */
			rpl = new_cs & 3;
			if (rpl > cpl)
				raise_exception_err(EXCP0D_GPF, new_cs & 0xfffc);
			if (dpl != cpl)
				raise_exception_err(EXCP0D_GPF, new_cs & 0xfffc);
		}
		if (!(e2 & DESC_P_MASK))
			raise_exception_err(EXCP0B_NOSEG, new_cs & 0xfffc);
		limit = get_seg_limit(e1, e2);
		if (new_eip > limit)
			raise_exception_err(EXCP0D_GPF, new_cs & 0xfffc);
		cpu_load_seg_cache(env, R_CS, (new_cs & 0xfffc) | cpl,
			get_seg_base(e1, e2), limit, e2);
//		env->eip = new_eip;
	} else {
		/* jump to call or task gate */
	}
}
